package com.demo.base.utils;

import com.demo.base.json.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.function.BiPredicate;

/**
 * Spring BeanUtils简单封装
 * Created by ethan on 2016/7/7.
 */
public abstract class ObjectUtils {

    public static <T> T fillProperties(Map<String, Object> properties, Class<T> tClass) {
        return new JSONObject(properties).toJavaObject(tClass);
    }

    public static <T> T fillProperties(Map<String, Object> properties, T target, Class<T> tClass) {
        T source = fillProperties(properties, tClass);
        return copyProperties(source, target);
    }

    public static <S, T> T copyProperties(S source, T target) {
        if (source == null) return null;
        copyProperties(source, target, (name, value) -> true);
        return target;
    }

    public static <S, T> T copyProperties(S source, T target, boolean ignoreNullValue) {
        if (source == null) return null;
        copyProperties(source, target, (name, value) -> !ignoreNullValue || null != value);
        return target;
    }

    public static <S, T> T copyProperties(S source, T target, String... ignoreProperties) {
        if (source == null) return null;
        copyProperties(source, target, (name, value) -> Arrays.asList(ignoreProperties).contains(name));
        return target;
    }

    /**
     * 将source中的属性值复制到target中的同名字段中
     */
    public static void copyProperties(Object source, Object target, BiPredicate<String, Object> propertyFilter) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class actualEditable = target.getClass();

        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);

        for (int idx = 0; idx < targetPds.length; idx++) {
            PropertyDescriptor targetPd = targetPds[idx];
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null) {
                PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }

                            Object value = readMethod.invoke(source, new Object[0]);
                            if (propertyFilter != null && !propertyFilter.test(targetPd.getName(), value)) {
                                continue;
                            }
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }

                            writeMethod.invoke(target, new Object[]{value});
                        } catch (Throwable ex) {
                            throw new FatalBeanException("Could not copy property \'" + targetPd.getName() + "\' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

}
